package org.infodavid.common.persistence;

import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.util.Arrays;
import java.util.Iterator;

import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import javax.sql.XAConnection;
import javax.sql.XADataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infodavid.common.persistence.impl.ConnectionHelperRegistryImpl;
import org.infodavid.common.registry.impl.MapRegistry;

/**
 * The Class ConnectionHelperRegistry.
 */
public abstract class ConnectionHelperRegistry extends MapRegistry<String,IConnectionHelper> {

  /** The Constant LOGGER. */
  private static final Log LOGGER = LogFactory.getLog(ConnectionHelperRegistry.class);

  /** The singleton. */
  protected static final ConnectionHelperRegistry SINGLETON;

  static {
    try {
      final Constructor<ConnectionHelperRegistryImpl> constructor =
          ConnectionHelperRegistryImpl.class.getDeclaredConstructor();
      constructor.setAccessible(true);

      SINGLETON = constructor.newInstance();
    }
    catch (final Exception e) {
      LOGGER.fatal("Can not instantiate the " + ConnectionHelperRegistry.class.getName(), e);

      throw new Error(e);
    }
  }

  /**
   * Gets the instance.
   * @return the factory.
   */
  public static ConnectionHelperRegistry getInstance() {
    return SINGLETON;
  }

  /**
   * The Class KeyGeneratorImpl.
   */
  protected static class KeyGeneratorImpl implements IKeyGenerator<String,IConnectionHelper> {
    /**
     * See super class or interface. (non-Javadoc)
     * @see org.infodavid.common.registry.impl.AbstractRegistry.IKeyGenerator#getKeys(java.io.Serializable)
     */
    @Override
    public String[] getKeys(final IConnectionHelper elt) {
      final String[] keys = elt == null ? null : elt.getTypes();

      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Generated keys: " + Arrays.toString(keys));
      }

      return keys;
    }
  }

  /**
   * The constructor.
   */
  protected ConnectionHelperRegistry() {
    super(new KeyGeneratorImpl());
  }

  /**
   * Open a new JDBC connection using the given parameters.
   * @param conf the connection parameters
   * @return the connection
   * @throws PersistenceException the persistence exception
   */
  public abstract Connection openConnection(IConnectionConfiguration conf)
      throws PersistenceException;

  /**
   * Open data source.
   * @param conf the connection parameters
   * @return the data source
   * @throws PersistenceException the persistence exception
   */
  public abstract DataSource openDataSource(IConnectionConfiguration conf)
      throws PersistenceException;

  /**
   * Open XA connection.
   * @param conf the connection parameters
   * @return the distributed connection
   * @throws PersistenceException the persistence exception
   */
  public abstract XAConnection openXaConnection(IConnectionConfiguration conf)
      throws PersistenceException;

  /**
   * Open XA data source.
   * @param conf the connection parameters
   * @return the distributed data source
   * @throws PersistenceException the persistence exception
   */
  public abstract XADataSource openXaDataSource(IConnectionConfiguration conf)
      throws PersistenceException;

  /**
   * Creates the new schema using the system connection.
   * @param configuration the configuration
   * @param connection the connection
   * @throws PersistenceException the persistence exception
   */
  public abstract void createSchema(Connection connection, IConnectionConfiguration configuration)
      throws PersistenceException;

  /**
   * Creates the user.
   * @param type the type of the database
   * @param connection the connection
   * @param username the user name
   * @param password the password
   * @throws PersistenceException the persistence exception
   */
  public abstract void createUser(String type, Connection connection, String username,
      String password) throws PersistenceException;

  /**
   * Delete user.
   * @param type the type of the database
   * @param connection the connection
   * @param username the user name
   * @throws PersistenceException the persistence exception
   */
  public abstract void deleteUser(String type, Connection connection, String username)
      throws PersistenceException;

  /**
   * Checks if the schema exists using the system connection.
   * @param type the type of the database
   * @param name the name of the schema
   * @param connection the connection
   * @throws PersistenceException the persistence exception
   */
  public abstract boolean hasSchema(String type, Connection connection, String name)
      throws PersistenceException;

  /**
   * Build the connection string using the given parameters.
   * @param conf the connection parameters
   * @return the connection string
   * @throws PersistenceException the persistence exception
   */
  public abstract String getConnectionString(IConnectionConfiguration conf)
      throws PersistenceException;

  /**
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.registry.impl.MapRegistry#doGetEntry(java.io.Serializable)
   */
  @Override
  protected IConnectionHelper doGetEntry(final String key) {
    IConnectionHelper helper = super.doGetEntry(key);

    if (helper == null) {
      // search in entries using ignore case
      LOGGER.debug("Searching in entries...");

      final Iterator<IConnectionHelper> ite = getEntries().iterator();
      IConnectionHelper entry;
      String[] types;
      int i;

      while (helper == null && ite.hasNext()) {
        entry = ite.next();
        i = 0;
        types = entry.getTypes();

        while (helper == null && i < types.length) {
          helper = types[i++].equalsIgnoreCase(key) ? entry : null;
        }

        // store the result for later use and avoid new search in entries for the same passed key
        if (helper != null) {
          LOGGER.debug("Adding helper in registry for the given key: " + key);

          getMap().put(key, helper);
        }
      }
    }

    return helper;
  }
}
